﻿using UnityEngine;
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;

namespace Hont.HyperWigglePackage
{
    public class HyperWiggleUtil
    {
        public static IHyperWiggleData GetHyperWiggleData(string xml)
        {
            var po = XmlDeserialize<HyperWigglePO>(xml);

            var result = new WiggleData();
            result.FPS = po.FPS;
            result.PRPair = po.Data;

            return result;
        }

        public static void WriteAllText(string filePath, string content)
        {
            if (File.Exists(filePath)) File.Delete(filePath);
            File.AppendAllText(filePath, content);
        }

        #region --- Common ---

        public static Vector3 Round(Vector3 vector, int roundCount)
        {
            return new Vector3(float.Parse(vector.x.ToString("f" + roundCount))
                , float.Parse(vector.y.ToString("f" + roundCount))
                , float.Parse(vector.z.ToString("f" + roundCount)));
        }

        public static bool IsInvalidWiggle(PRPair[] source, out Vector3 invalidPosition, out Vector3 invalidRotation)
        {
            bool result = false;
            var position = Vector3.zero;
            var rotation = Vector3.zero;

            for (int i = 0; i < source.Length; i++)
            {
                position += source[i].RelativePosition;
                rotation += source[i].RelativeRotation;
            }

            result |= position != Vector3.zero;
            result |= rotation != Vector3.zero;

            invalidPosition = position;
            invalidRotation = rotation;

            return result;
        }

        public static void StableOffset(PRPair[] source)
        {
            var invalidPosition = Vector3.zero;
            var invalidRotation = Vector3.zero;
            var isInvalidWiggle = IsInvalidWiggle(source, out invalidPosition, out invalidRotation);

            if (isInvalidWiggle)
            {
                var avePosition = -invalidPosition / source.Length;
                var aveRotation = -invalidRotation / source.Length;

                for (int i = 0; i < source.Length; i++)
                {
                    var item = source[i];

                    item.RelativePosition += avePosition;
                    item.RelativeRotation += aveRotation;

                    source[i] = item;
                }
            }

            invalidPosition = Vector3.zero;
            invalidRotation = Vector3.zero;
            isInvalidWiggle = IsInvalidWiggle(source, out invalidPosition, out invalidRotation);

            if (invalidPosition.sqrMagnitude < 0.0001f)
                source[source.Length - 1].RelativePosition -= invalidPosition;

            if (invalidRotation.sqrMagnitude < 0.0001f)
                source[source.Length - 1].RelativeRotation -= invalidRotation;
        }

        public static void ConvertPRPair(PRPair[] source)
        {
            var position = Vector3.zero;
            var rotation = Vector3.zero;
            for (int i = 0; i < source.Length; i++)
            {
                var item = source[i];
                position += item.RelativePosition;
                rotation += item.RelativeRotation;
                item.AbsolutePosition = position;
                item.AbsoluteRotation = rotation;
                source[i] = item;
            }
        }

        #endregion

        #region --- Serialize ---

        public static MemoryStream XmlSerializationToMemory(object obj, params Type[] extraTypeArr)
        {
            var setting = new XmlWriterSettings();
            setting.Encoding = new UTF8Encoding(false);
            setting.Indent = true;

            var xmlSer = new XmlSerializer(obj.GetType(), extraTypeArr);
            var stream = new MemoryStream();

            using (var writer = XmlWriter.Create(stream, setting))
            {
                xmlSer.Serialize(writer, obj);
            }

            return stream;
        }

        public static string XmlSerialize(object obj, params Type[] extraTypeArr)
        {
            if (obj == null) return "";

            var memoryStream = XmlSerializationToMemory(obj, extraTypeArr);

            var sr = new StreamReader(memoryStream);
            var str = Encoding.UTF8.GetString(memoryStream.ToArray());

            memoryStream.Flush();
            memoryStream.Close();
            sr.Close();

            return str;
        }

        public static T XmlDeserialize<T>(string xml, params Type[] extraTypeArr)
            where T : class
        {
            if (string.IsNullOrEmpty(xml)) return default(T);

            var xmlSer = new XmlSerializer(typeof(T), extraTypeArr);
            var reader = new StringReader(xml);
            return (T)xmlSer.Deserialize(reader);
        }

        #endregion

        #region --- Reflection ---

        public static FieldInfo[] GetObjectPublicFields(object obj)
        {
            return obj
                .GetType()
                .GetFields(BindingFlags.Instance | BindingFlags.Public);
        }

        public static Type GetRuntimeType(string typeFullName)
        {
            if (typeFullName == null || typeFullName.Length == 0) return null;

            var result = Type.GetType(typeFullName);

            if (result == null)
            {
                var assembly = Assembly.Load("Assembly-CSharp");
                result = assembly.GetType(typeFullName);
            }

            if (result == null)
            {
                var assembly = Assembly.Load("UnityEngine");
                result = assembly.GetType(typeFullName);
            }

            return result;
        }

        public static TInterface[] GetAssemblyInterfacesAndCreate<TInterface>(object assemblyReferenceObject)
        {
            return GetAssemblyInterfacesAndCreate<TInterface>(Assembly.GetAssembly(assemblyReferenceObject.GetType()));
        }

        public static TInterface[] GetAssemblyInterfacesAndCreate<TInterface>(Assembly assembly)
        {
            var types = GetAssemblyInterfaceTypes(assembly, typeof(TInterface));
            var result = new TInterface[types.Length];

            for (int i = 0; i < result.Length; i++)
            {
                if (types[i].IsAbstract) continue;

                result[i] = GetAssemblyCreateInstance<TInterface>(assembly, types[i]);
            }

            return result;
        }

        public static Type[] GetAssemblyInterfaceTypes(Assembly assembly, Type type)
        {
            var types = assembly.GetTypes()
                .Where(m => m.GetInterface(type.Name) != null)
                .ToArray();

            return types;
        }

        public static T GetAssemblyCreateInstance<T>(Assembly assembly, Type type)
        {
            return (T)assembly.CreateInstance(type.FullName);
        }

        #endregion
    }
}
